package com.caregiver.watch.business.data.service.impl;

import com.alibaba.fastjson2.JSON;
import com.caregiver.watch.business.data.dao.HealthBaseDataDao;
import com.caregiver.watch.business.data.dao.HealthBloodOxygenDao;
import com.caregiver.watch.business.data.dao.HealthBloodPressureDao;
import com.caregiver.watch.business.data.dao.HealthBloodPressureHeartRateDao;
import com.caregiver.watch.business.data.dao.HealthDeviceLocationDao;
import com.caregiver.watch.business.data.dao.HealthEcgDao;
import com.caregiver.watch.business.data.dao.HealthHeartRateDao;
import com.caregiver.watch.business.data.dao.HealthHeartRateVariabilityDao;
import com.caregiver.watch.business.data.dao.HealthRriDao;
import com.caregiver.watch.business.data.dao.HealthSleepRawDao;
import com.caregiver.watch.business.data.dao.HealthTemperatureDao;
import com.caregiver.watch.business.data.dao.SportDataDao;
import com.caregiver.watch.business.data.helper.DataPreprocessorHelper;
import com.caregiver.watch.business.data.model.po.HealthBaseData;
import com.caregiver.watch.business.data.model.po.HealthBloodOxygen;
import com.caregiver.watch.business.data.model.po.HealthBloodPressure;
import com.caregiver.watch.business.data.model.po.HealthBloodPressureHeartRate;
import com.caregiver.watch.business.data.model.po.HealthDeviceLocation;
import com.caregiver.watch.business.data.model.po.HealthEcg;
import com.caregiver.watch.business.data.model.po.HealthHeartRate;
import com.caregiver.watch.business.data.model.po.HealthHeartRateVariability;
import com.caregiver.watch.business.data.model.po.HealthRri;
import com.caregiver.watch.business.data.model.po.HealthSleepRaw;
import com.caregiver.watch.business.data.model.po.HealthTemperature;
import com.caregiver.watch.business.data.model.po.SportData;
import com.caregiver.watch.business.data.protobuf.HisDataOuterClass;
import com.caregiver.watch.business.data.protobuf.HisEcgData;
import com.caregiver.watch.business.data.protobuf.HisHealthData;
import com.caregiver.watch.business.data.protobuf.HisRriData;
import com.caregiver.watch.business.data.protobuf.RealtimeData;
import com.caregiver.watch.business.data.service.HealthDataService;
import com.caregiver.watch.business.data.utils.ByteUtils;
import com.caregiver.watch.common.constant.NumberConstants;
import com.caregiver.watch.common.utils.collection.ListUtils;
import com.caregiver.watch.common.utils.time.DateUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import static com.caregiver.watch.business.data.common.DataConstant.CONTINUOUS_SPORT_INTERVAL_TIME;
import static com.caregiver.watch.business.data.protobuf.HisHealthData.TPAMeasureType.TPA_MEASURE_TYPE_AUTO;
import static com.caregiver.watch.common.constant.NumberConstants.BIG_DECIMAL_HUNDRED;
import static com.caregiver.watch.common.constant.NumberConstants.BIG_DECIMAL_ZERO_POINT_ONE;
import static com.caregiver.watch.common.constant.NumberConstants.ONE;
import static com.caregiver.watch.common.constant.NumberConstants.ZERO;
import static com.caregiver.watch.common.constant.NumberConstants.ZERO_POINT_ONE;

/**
 * @Description: 健康数据服务
 * @Author 疆戟
 * @Date 2025/9/12 20:46
 * @Version 1.0
 */
@AllArgsConstructor
@Service
@Slf4j
public class HealthDataServiceImpl implements HealthDataService {

    private final HealthBaseDataDao healthBaseDataDao;
    private final SportDataDao sportDataDao;
    private final HealthHeartRateDao healthHeartRateDao;
    private final HealthBloodOxygenDao healthBloodOxygenDao;
    private final HealthBloodPressureDao healthBloodPressureDao;
    private final HealthHeartRateVariabilityDao healthHeartRateVariabilityDao;
    private final HealthTemperatureDao healthTemperatureDao;
    private final HealthBloodPressureHeartRateDao healthBloodPressureHeartRateDao;
    private final HealthEcgDao healthEcgDao;
    private final HealthSleepRawDao healthSleepRawDao;
    private final HealthDeviceLocationDao healthDeviceLocationDao;
    private final HealthRriDao healthRriDao;

    @Override
    public void insertHealthData(RealtimeData.RtHealth health, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (Objects.isNull(health)) {
            return;
        }
        // 实时距离
        BigDecimal distance = health.hasDistance()? new BigDecimal(health.getDistance()).multiply(BIG_DECIMAL_ZERO_POINT_ONE) : BigDecimal.ZERO;
        // 卡路里
        BigDecimal calorie = health.hasCalorie() ? new BigDecimal(health.getCalorie()).multiply(BIG_DECIMAL_ZERO_POINT_ONE) : BigDecimal.ZERO;
        // 实时步数
        int steps = health.hasSteps() ? health.getSteps() : ZERO;
        // 计算站立时间
        List<SportData> sportData = sportDataDao.queryStepCountAndDataTime(dataTime.with(LocalTime.MIN), dataTime, userId, deviceUuId);
        // 活动时长
        int activityDuration = calculateDuration(sportData);
        // 计算站立时长 站立时长：按整点计算，1个小时内某1分钟的步数>80步即可认为站立时长+1。
        int standTime = calculateStandTime(sportData);
        log.info("{} steps:{}, distance:{}, calorie:{}, standTime:{} duration:{}", dataTime, steps, distance, calorie, standTime, activityDuration);
        HealthBaseData healthBaseData = new HealthBaseData();
        healthBaseData.setUserId(userId);
        healthBaseData.setDeviceUuid(deviceUuId);
        healthBaseData.setDataTime(dataTime);
        healthBaseData.setStepCount(steps);
        healthBaseData.setDistance(distance);
        healthBaseData.setCalorie(calorie);
        healthBaseData.setStandTime(standTime);
        healthBaseData.setActivityDuration(activityDuration);
        try {
            int insert = healthBaseDataDao.insert(healthBaseData);
            log.info("insertHealthData:{}", insert);
        } catch (Exception e) {
            log.error("insertHealthData error:{}", e.getMessage());
        }
    }

    /**
     * 计算活动时长
     *
     * @param sportData
     * @return
     */
    private int calculateDuration(List<SportData> sportData) {
        int duration = ZERO;
        LocalDateTime endTime = null;
        for (SportData data : sportData) {
            if (data == null || data.getDataTime() == null) {
                continue;
            }
            if (endTime != null) {
                long minutes = DateUtils.getTimeBetween(endTime, data.getDataTime(), TimeUnit.MINUTES);;
                if (minutes < CONTINUOUS_SPORT_INTERVAL_TIME) {
                    duration += minutes;
                }
            }
            endTime = data.getDataTime();
        }
        return duration;
    }

    /**
     * 计算站立时长
     *
     * @param sportData
     * @return
     */
    private int calculateStandTime(List<SportData> sportData) {
        Map<Integer, List<SportData>> hourToSportDataMap = ListUtils.groupingBy(
                ListUtils.filter(sportData, data -> Objects.nonNull(data.getDataTime())),
                data -> data.getDataTime().getHour());

        int standTime = ZERO;
        for (Map.Entry<Integer, List<SportData>> entry : hourToSportDataMap.entrySet()) {
            List<SportData> hourSportData = entry.getValue();
            if (CollectionUtils.isNotEmpty(hourSportData) &&
                    hourSportData.stream()
                            .anyMatch(data -> Objects.nonNull(data.getStepCount()) && data.getStepCount() > NumberConstants.EIGHTY)) {
                standTime += ONE;
            }
        }
        return standTime;
    }

    @Override
    public void insertSportData(HisHealthData.HisDataHealth healthData,
                                String deviceUuId,
                                Long userId,
                                LocalDateTime dataTime) {
        if (!healthData.hasPedoData()) {
            return;
        }
        try {
            HisHealthData.HisHealthPedo hisPedo = healthData.getPedoData();
            // 运动类型
            int type = hisPedo.getType();
            // 运动状态
            int state = hisPedo.getState();
            // 步数
            int step = hisPedo.getStep();
            //  距离 单位米 获取数据除以10才是真正的数据
            BigDecimal distance = new BigDecimal(hisPedo.getDistance()).multiply(new BigDecimal(ZERO_POINT_ONE));
            // 卡路里 获取数据除以10才是真正的数据,
            BigDecimal calorie = new BigDecimal(hisPedo.getCalorie()).multiply(new BigDecimal(ZERO_POINT_ONE));
            log.info("userId:{} deviceUuId:{} {} step-{}, distance-{}, calorie-{}", userId, deviceUuId, dataTime, hisPedo.getStep(), distance, calorie);
            SportData sportData = new SportData();
            sportData.setUserId(userId);
            sportData.setDeviceUuId(deviceUuId);
            sportData.setDataTime(dataTime);
            sportData.setSportType(type);
            sportData.setSportState(state);
            sportData.setStepCount(step);
            sportData.setDistance(distance);
            sportData.setCalorie(calorie);
            int insert = sportDataDao.insert(sportData);
            log.info("insertSportData:{}", insert);
        } catch (Exception e) {
            log.error("insertSportData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertHeartRateData(HisHealthData.HisDataHealth healthData,
                                    String deviceUuId,
                                    Long userId,
                                    LocalDateTime dataTime) {
        if (!healthData.hasHrData()) {
            return;
        }
        try {
            HisHealthData.HisHealthHr hisHr = healthData.getHrData();
            log.info("userId:{} deviceUuId:{} {} avg bpm-{}, max bpm-{}, min bpm-{}", userId, deviceUuId, dataTime, hisHr.getAvgBpm(), hisHr.getMaxBpm(), hisHr.getMinBpm());
            HealthHeartRate healthHeartRate = new HealthHeartRate();
            healthHeartRate.setUserId(userId);
            healthHeartRate.setDeviceUuid(deviceUuId);
            healthHeartRate.setRecordedAt(dataTime);
            healthHeartRate.setMinBpm(hisHr.getMinBpm());
            healthHeartRate.setMaxBpm(hisHr.getMaxBpm());
            healthHeartRate.setAvgBpm(hisHr.getAvgBpm());
            healthHeartRate.setCreatedAt(LocalDateTime.now());
            int insert = healthHeartRateDao.insert(healthHeartRate);
            log.info("insertHeartRateData:{}", insert);
        } catch (Exception e) {
            log.error("insertHeartRateData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertBloodOxygen(HisHealthData.HisDataHealth healthData, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (!healthData.hasBxoyData()) {
            return;
        }
        try {
            HisHealthData.HisHealthBOxy hisBoxy = healthData.getBxoyData();
            log.info("userId:{} deviceUuId:{} {} avg boxy-{}, max boxy-{}, min boxy-{}", userId, deviceUuId, dataTime, hisBoxy.getAgvOxy(), hisBoxy.getMaxOxy(), hisBoxy.getMinOxy());
            HealthBloodOxygen healthSpo2 = new HealthBloodOxygen();
            healthSpo2.setUserId(userId);
            healthSpo2.setDeviceUuid(deviceUuId);
            healthSpo2.setRecordedAt(dataTime);
            healthSpo2.setMinOxygen(new BigDecimal(hisBoxy.getMinOxy()));
            healthSpo2.setMaxOxygen(new BigDecimal(hisBoxy.getMaxOxy()));
            healthSpo2.setAvgOxygen(new BigDecimal(hisBoxy.getAgvOxy()));
            healthSpo2.setCreatedAt(LocalDateTime.now());
            int insert = healthBloodOxygenDao.insert(healthSpo2);
            log.info("insertSpo2Data:{}", insert);
        } catch (Exception e) {
            log.error("insertSpo2Data error:{}", e.getMessage());
        }
    }

    @Override
    public void insertBpData(HisHealthData.HisDataHealth healthData, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (!healthData.hasBpData()) {
            return;
        }
        try {
            HisHealthData.HisHealthBp bpData = healthData.getBpData();
            log.info("userId:{} deviceUuId:{} {} sbp-{}, dbp-{}", userId, deviceUuId, dataTime, bpData.getSbp(), bpData.getDbp());
            HealthBloodPressure healthBloodPressure = new HealthBloodPressure();
            healthBloodPressure.setUserId(userId);
            healthBloodPressure.setDeviceUuid(deviceUuId);
            healthBloodPressure.setRecordedAt(dataTime);
            healthBloodPressure.setSystolic(bpData.getSbp());
            healthBloodPressure.setDiastolic(bpData.getDbp());
            healthBloodPressure.setCreatedAt(LocalDateTime.now());
            int insert = healthBloodPressureDao.insert(healthBloodPressure);
            log.info("insertBpData:{}", insert);
        } catch (Exception e) {
            log.error("insertBpData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertFatigueData(HisHealthData.HisDataHealth healthData, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (!healthData.hasHrvData()) {
            return;
        }
        try {
            HisHealthData.HisHealthHrv hisHrv = healthData.getHrvData();
            log.info("userId:{} deviceUuId:{} {} rmssd-{}, fatigue-{}", userId, deviceUuId, dataTime, hisHrv.getRMSSD(), hisHrv.getFatigue());
            int fatigue = (int) hisHrv.getFatigue();
            if (fatigue <= 0) {
                fatigue = (int) (Math.log(hisHrv.getRMSSD()) * 20);
            }
            HealthHeartRateVariability healthHeartRateVariability = new HealthHeartRateVariability();
            healthHeartRateVariability.setUserId(userId);
            healthHeartRateVariability.setDeviceUuid(deviceUuId);
            healthHeartRateVariability.setRecordedAt(dataTime);
            healthHeartRateVariability.setRmssd(new BigDecimal(String.valueOf(hisHrv.getRMSSD())));
            healthHeartRateVariability.setFatigue(fatigue);
            healthHeartRateVariability.setStress(NumberConstants.HUNDRED - fatigue);
            healthHeartRateVariability.setCreatedAt(LocalDateTime.now());
            int insert = healthHeartRateVariabilityDao.insert(healthHeartRateVariability);
            log.info("insertFatigueData:{}", insert);
        } catch (Exception e) {
            log.error("insertFatigueData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertTemperatureData(HisHealthData.HisDataHealth healthData, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (!healthData.hasTemperatureData()) {
            return;
        }
        try {
            HealthTemperature healthTemperature = new HealthTemperature();
            healthTemperature.setUserId(userId);
            healthTemperature.setDeviceUuid(deviceUuId);
            healthTemperature.setRecordedAt(dataTime);
            HisHealthData.HisHealthTemp hisTemp = healthData.getTemperatureData();
            healthTemperature.setEviBody(hisTemp.getEviBody());
            healthTemperature.setEstiArm(hisTemp.getEstiArm());
            // 体表温度
            BigDecimal shellTemp = new BigDecimal(ByteUtils.loword(hisTemp.getEviBody())).multiply(BIG_DECIMAL_HUNDRED);
            healthTemperature.setShellTemp(shellTemp);
            // 环境温度
            BigDecimal envTemp = new BigDecimal(ByteUtils.hiword(hisTemp.getEviBody())).multiply(BIG_DECIMAL_HUNDRED);
            healthTemperature.setEnvTemp(envTemp);
            // 腋窝温度
            BigDecimal axillaryTemp = new BigDecimal(ByteUtils.loword(hisTemp.getEstiArm())).multiply(BIG_DECIMAL_HUNDRED);
            healthTemperature.setAxillaryTemp(axillaryTemp);
            // 估算体温温度
            BigDecimal estTemp = new BigDecimal(ByteUtils.hiword(hisTemp.getEstiArm())).multiply(BIG_DECIMAL_HUNDRED);
            healthTemperature.setEstTemp(estTemp);
            // 测量类型
            HisHealthData.TPAMeasureType measureType = hisTemp.getType();
            log.info("userId:{} deviceUuId:{} {} env-{}, est-{}, axillary-{}, shell-{} measureType:{}", userId, deviceUuId, dataTime, envTemp, estTemp, axillaryTemp, shellTemp, measureType);
            if (measureType != TPA_MEASURE_TYPE_AUTO) {
                // 温度测量未完成，仍在计算中，不可用
                return;
            }
            healthTemperature.setType(measureType.getNumber());
            healthTemperature.setCreatedAt(LocalDateTime.now());
            int insert = healthTemperatureDao.insert(healthTemperature);
            log.info("insertTemperatureData:{}", insert);
        } catch (Exception e) {
            log.error("insertTemperatureData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertBpHeartRateData(HisHealthData.HisDataHealth healthData, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (!healthData.hasBpBpmData()) {
            return;
        }
        HisHealthData.HisHealthbp_bpm bpBpmData = healthData.getBpBpmData();
        int bpHr = bpBpmData.getBpm();
        log.info("userId:{} deviceUuId:{} bp hr:{}", userId, deviceUuId, bpHr);
        HealthBloodPressureHeartRate healthBloodPressureHeartRate = new HealthBloodPressureHeartRate();
        healthBloodPressureHeartRate.setUserId(userId);
        healthBloodPressureHeartRate.setDeviceUuid(deviceUuId);
        healthBloodPressureHeartRate.setRecordedAt(dataTime);
        healthBloodPressureHeartRate.setBpm(bpHr);
        healthBloodPressureHeartRate.setCreatedAt(LocalDateTime.now());
        try {

            int insert = healthBloodPressureHeartRateDao.insert(healthBloodPressureHeartRate);
            log.info("insertBpHeartRateData:{}", insert);
        } catch (Exception e) {
            log.error("insertBpHeartRateData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertEcgData(HisEcgData.HisDataECG ecgData, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (Objects.isNull(ecgData)) {
            return;
        }
        log.info("userId:{} deviceUuId:{} ecg time:{}, count:{}", userId, deviceUuId, dataTime, ecgData.getRawDataCount());
        HealthEcg healthEcg = new HealthEcg();
        healthEcg.setUserId(userId);
        healthEcg.setDeviceUuid(deviceUuId);
        healthEcg.setRecordedAt(dataTime);
        healthEcg.setRawData(JSON.toJSONString(ecgData.getRawDataList()));
        healthEcg.setCreatedAt(LocalDateTime.now());
        try {

            int insert = healthEcgDao.insert(healthEcg);
            log.info("insertEcgData:{}", insert);
        } catch (Exception e) {
            log.error("insertEcgData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertSleepData(HisDataOuterClass.HisData hisData, String deviceUuId, Long userId, LocalDateTime dataTime) {
        // 睡眠数据
        String sleepData = DataPreprocessorHelper.sleepPreprocess(hisData);
        log.info("userId:{} deviceUuId:{} sleep data:{}",  userId, deviceUuId, sleepData);
        if (StringUtils.isEmpty(sleepData)) {
            return;
        }
        HealthSleepRaw healthSleep = new HealthSleepRaw();
        healthSleep.setUserId(userId);
        healthSleep.setDeviceUuid(deviceUuId);
        healthSleep.setRecordedAt(dataTime);
        healthSleep.setRawData(sleepData);
        healthSleep.setCreatedAt(LocalDateTime.now());
        try {
            // 记录原始睡眠数据
            int insert = healthSleepRawDao.insert(healthSleep);
            log.info("insertSleepData:{}", insert);
        } catch (Exception e) {
            log.error("insertSleepData error:{}", e.getMessage());
        }
    }

    @Override
    public void insertGnssData(List<HealthDeviceLocation> deviceLocations) {
        if (CollectionUtils.isEmpty(deviceLocations)) {
            return;
        }
        try {
            int count = healthDeviceLocationDao.batchInsert(deviceLocations);
            log.info("insertGnssData:{}", count);
        } catch (Exception e) {
            log.error("insertGnssData error:{}", e.getMessage());
        }
    }


    @Override
    public void insertRriData(HisRriData.HisDataRRI rri, String deviceUuId, Long userId, LocalDateTime dataTime) {
        if (Objects.isNull(rri)) {
            return;
        }
        log.info("userId:{} deviceUuId:{} rri time:{}, count:{}", userId, deviceUuId, dataTime, rri.getRawDataCount());
        HealthRri healthRri = new HealthRri();
        healthRri.setUserId(userId);
        healthRri.setDeviceUuid(deviceUuId);
        healthRri.setRecordedAt(dataTime);
        List<Short> shorts = ListUtils.flatMap(rri.getRawDataList(), item -> {
            short fval = (short) ((item >> 16) & 0x0000ffff);
            short sval = (short) (item & 0x0000ffff);
            return Stream.of(fval, sval);
        });
        healthRri.setRawData(JSON.toJSONString(shorts));
        healthRri.setCreatedAt(LocalDateTime.now());
        try {
            int insert = healthRriDao.insert(healthRri);
            log.info("insetRriData:{}", insert);
        } catch (Exception e) {
            log.error("insetRriData error:{}", e.getMessage());
        }
    }
}
